package com.example.framework.swagger;

import cn.hutool.core.util.StrUtil;
import io.swagger.v3.oas.models.Components;
import io.swagger.v3.oas.models.OpenAPI;
import io.swagger.v3.oas.models.info.Contact;
import io.swagger.v3.oas.models.info.Info;
import io.swagger.v3.oas.models.info.License;
import io.swagger.v3.oas.models.parameters.Parameter;
import io.swagger.v3.oas.models.security.SecurityRequirement;
import io.swagger.v3.oas.models.security.SecurityScheme;
import lombok.extern.slf4j.Slf4j;
import org.springdoc.core.models.GroupedOpenApi;
import org.springdoc.core.utils.Constants;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;

import java.util.HashMap;
import java.util.Map;
import java.util.Optional;

@Slf4j
@AutoConfiguration
@ConditionalOnClass({OpenAPI.class})
@EnableConfigurationProperties(SwaggerProperties.class)
@ConditionalOnProperty(name = Constants.SPRINGDOC_ENABLED, havingValue = "true", matchIfMissing = true)
public class SwaggerAutoConfiguration {

    @Value("${spring.application.name}")
    private String serviceName;

    // jwt 认证的请求头，如果不引入spring-boot-starter-security，这里可以不用配置
    @Value("${security.headerName:}")
    private String jwtHeaderName;

    private boolean needSecurityHeader() {
        return StrUtil.isNotBlank(jwtHeaderName);
    }

    @Bean
    public OpenAPI openAPI(SwaggerProperties properties) {
        OpenAPI openAPI = new OpenAPI()
                // 接口信息
                .info(buildInfo(properties));
        if (needSecurityHeader()) {
            Map<String, SecurityScheme> securitySchemas = buildSecuritySchemes();
            // 接口安全配置
            openAPI.components(new Components().securitySchemes(securitySchemas));
            securitySchemas.keySet().forEach(key -> openAPI.addSecurityItem(new SecurityRequirement().addList(key)));
        }
        log.debug("创建swagger的OpenAPI成功");
        return openAPI;
    }

    /**
     * 安全模式，这里配置通过请求头 Authorization 传递 token 参数
     */
    private Map<String, SecurityScheme> buildSecuritySchemes() {
        Map<String, SecurityScheme> securitySchemes = new HashMap<>();
        SecurityScheme securityScheme = new SecurityScheme()
                .type(SecurityScheme.Type.APIKEY) // 类型
                .name(jwtHeaderName) // 请求头的 name
                .in(SecurityScheme.In.HEADER); // token 所在位置
        securitySchemes.put("全局认证 Token", securityScheme);
        return securitySchemes;
    }

    /**
     * API 摘要信息
     */
    private Info buildInfo(SwaggerProperties properties) {
        return new Info()
                .title(Optional.ofNullable(properties.getTitle()).orElse(serviceName))
                .description(properties.getDescription())
                .version(properties.getVersion())
                .contact(new Contact().name(properties.getAuthor()).url(properties.getUrl()).email(properties.getEmail()))
                .license(new License().name(properties.getLicense()).url(properties.getLicenseUrl()));
    }

    // ========== 分组 OpenAPI 配置 ==========

    /**
     * 所有模块的 API 分组
     */
    @Bean
    public GroupedOpenApi allGroupedOpenApi() {
        return buildGroupedOpenApi("all", "");
    }

    public GroupedOpenApi buildGroupedOpenApi(String group) {
        return buildGroupedOpenApi(group, group);
    }

    public GroupedOpenApi buildGroupedOpenApi(String group, String path) {
        // 确保路径以斜杠开始
        if (!path.startsWith("/")) {
            path = "/" + path;  // 如果没有斜杠，手动添加斜杠
        }
        GroupedOpenApi.Builder builder = GroupedOpenApi.builder()
                .group(group)
                .pathsToMatch(path + "/**");
        if (needSecurityHeader()) {
            builder.addOperationCustomizer((operation, handlerMethod) -> operation.addParametersItem(buildSecurityHeaderParameter()));
        }
        return builder.build();
    }

    /**
     * 构建 Authorization 认证请求头参数，在每个接口前都会带上这个参数，在 knife4j 中使用
     * <p>
     * 解决 Knife4j <a href="https://gitee.com/xiaoym/knife4j/issues/I69QBU">Authorize 未生效，请求header里未包含参数</a>
     *
     * @return 认证参数
     */
    private Parameter buildSecurityHeaderParameter() {
        return new Parameter()
                .name(jwtHeaderName) // header 名
                .description("认证 Token") // 描述
                .in(String.valueOf(SecurityScheme.In.HEADER)); // 请求 header
    }

}
